home *** CD-ROM | disk | FTP | other *** search
/ MacAddict 108 / MacAddict108.iso / Software / Internet & Communication / WordPress 1.5.1.dmg / wordpress / wp-includes / gettext.php < prev    next >
Encoding:
PHP Script  |  2005-04-20  |  10.5 KB  |  359 lines

  1. <?php
  2. /*
  3.    Copyright (c) 2003 Danilo Segan <danilo@kvota.net>.
  4.    Copyright (c) 2005 Nico Kaiser <nico@siriux.net>
  5.    
  6.    This file is part of PHP-gettext.
  7.  
  8.    PHP-gettext is free software; you can redistribute it and/or modify
  9.    it under the terms of the GNU General Public License as published by
  10.    the Free Software Foundation; either version 2 of the License, or
  11.    (at your option) any later version.
  12.  
  13.    PHP-gettext is distributed in the hope that it will be useful,
  14.    but WITHOUT ANY WARRANTY; without even the implied warranty of
  15.    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16.    GNU General Public License for more details.
  17.  
  18.    You should have received a copy of the GNU General Public License
  19.    along with PHP-gettext; if not, write to the Free Software
  20.    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  21.  
  22. */
  23.  
  24. /**
  25.  * Provides a simple gettext replacement that works independently from
  26.  * the system's gettext abilities.
  27.  * It can read MO files and use them for translating strings.
  28.  * The files are passed to gettext_reader as a Stream (see streams.php)
  29.  * 
  30.  * This version has the ability to cache all strings and translations to
  31.  * speed up the string lookup.
  32.  * While the cache is enabled by default, it can be switched off with the
  33.  * second parameter in the constructor (e.g. whenusing very large MO files
  34.  * that you don't want to keep in memory)
  35.  */
  36. class gettext_reader {
  37.   //public:
  38.    var $error = 0; // public variable that holds error code (0 if no error)
  39.    
  40.    //private:
  41.   var $BYTEORDER = 0;        // 0: low endian, 1: big endian
  42.   var $STREAM = NULL;
  43.   var $short_circuit = false;
  44.   var $enable_cache = false;
  45.   var $originals = NULL;      // offset of original table
  46.   var $translations = NULL;    // offset of translation table
  47.   var $pluralheader = NULL;    // cache header field for plural forms
  48.   var $total = 0;          // total string count
  49.   var $table_originals = NULL;  // table for original strings (offsets)
  50.   var $table_translations = NULL;  // table for translated strings (offsets)
  51.   var $cache_translations = NULL;  // original -> translation mapping
  52.  
  53.  
  54.   /* Methods */
  55.   
  56.     
  57.   /**
  58.    * Reads a 32bit Integer from the Stream
  59.    * 
  60.    * @access private
  61.    * @return Integer from the Stream
  62.    */
  63.   function readint() {
  64.       if ($this->BYTEORDER == 0) {
  65.         // low endian
  66.         return array_shift(unpack('V', $this->STREAM->read(4)));
  67.       } else {
  68.         // big endian
  69.         return array_shift(unpack('N', $this->STREAM->read(4)));
  70.       }
  71.     }
  72.  
  73.   /**
  74.    * Reads an array of Integers from the Stream
  75.    * 
  76.    * @param int count How many elements should be read
  77.    * @return Array of Integers
  78.    */
  79.   function readintarray($count) {
  80.     if ($this->BYTEORDER == 0) {
  81.         // low endian
  82.         return unpack('V'.$count, $this->STREAM->read(4 * $count));
  83.       } else {
  84.         // big endian
  85.         return unpack('N'.$count, $this->STREAM->read(4 * $count));
  86.       }
  87.   }
  88.   
  89.   /**
  90.    * Constructor
  91.    * 
  92.    * @param object Reader the StreamReader object
  93.    * @param boolean enable_cache Enable or disable caching of strings (default on)
  94.    */
  95.   function gettext_reader($Reader, $enable_cache = true) {
  96.     // If there isn't a StreamReader, turn on short circuit mode.
  97.     if (! $Reader) {
  98.       $this->short_circuit = true;
  99.       return;
  100.     }
  101.     
  102.     // Caching can be turned off
  103.     $this->enable_cache = $enable_cache;
  104.  
  105.     // $MAGIC1 = (int)0x950412de; //bug in PHP 5
  106.     $MAGIC1 = (int) - 1794895138;
  107.     // $MAGIC2 = (int)0xde120495; //bug
  108.     $MAGIC2 = (int) - 569244523;
  109.  
  110.     $this->STREAM = $Reader;
  111.     $magic = $this->readint();
  112.     if ($magic == $MAGIC1) {
  113.       $this->BYTEORDER = 0;
  114.     } elseif ($magic == $MAGIC2) {
  115.       $this->BYTEORDER = 1;
  116.     } else {
  117.       $this->error = 1; // not MO file
  118.       return false;
  119.     }
  120.     
  121.     // FIXME: Do we care about revision? We should.
  122.     $revision = $this->readint();
  123.     
  124.     $this->total = $this->readint();
  125.     $this->originals = $this->readint();
  126.     $this->translations = $this->readint();
  127.   }
  128.   
  129.   /**
  130.    * Loads the translation tables from the MO file into the cache
  131.    * If caching is enabled, also loads all strings into a cache
  132.    * to speed up translation lookups
  133.    * 
  134.    * @access private
  135.    */
  136.   function load_tables() {
  137.     if (is_array($this->cache_translations) &&
  138.       is_array($this->table_originals) &&
  139.       is_array($this->table_translations))
  140.       return;
  141.     
  142.     /* get original and translations tables */
  143.     $this->STREAM->seekto($this->originals);
  144.     $this->table_originals = $this->readintarray($this->total * 2);
  145.     $this->STREAM->seekto($this->translations);
  146.     $this->table_translations = $this->readintarray($this->total * 2);
  147.     
  148.     if ($this->enable_cache) {
  149.       $this->cache_translations = array ();
  150.       /* read all strings in the cache */
  151.       for ($i = 0; $i < $this->total; $i++) {
  152.         $this->STREAM->seekto($this->table_originals[$i * 2 + 2]);
  153.         $original = $this->STREAM->read($this->table_originals[$i * 2 + 1]);
  154.         $this->STREAM->seekto($this->table_translations[$i * 2 + 2]);
  155.         $translation = $this->STREAM->read($this->table_translations[$i * 2 + 1]);
  156.         $this->cache_translations[$original] = $translation;
  157.       }
  158.     }
  159.   }
  160.   
  161.   /**
  162.    * Returns a string from the "originals" table
  163.    * 
  164.    * @access private
  165.    * @param int num Offset number of original string
  166.    * @return string Requested string if found, otherwise ''
  167.    */
  168.   function get_original_string($num) {
  169.     $length = $this->table_originals[$num * 2 + 1];
  170.     $offset = $this->table_originals[$num * 2 + 2];
  171.     if (! $length)
  172.       return '';
  173.     $this->STREAM->seekto($offset);
  174.     $data = $this->STREAM->read($length);
  175.     return (string)$data;
  176.   }
  177.   
  178.   /**
  179.    * Returns a string from the "translations" table
  180.    * 
  181.    * @access private
  182.    * @param int num Offset number of original string
  183.    * @return string Requested string if found, otherwise ''
  184.    */
  185.   function get_translation_string($num) {
  186.     $length = $this->table_translations[$num * 2 + 1];
  187.     $offset = $this->table_translations[$num * 2 + 2];
  188.     if (! $length)
  189.       return '';
  190.     $this->STREAM->seekto($offset);
  191.     $data = $this->STREAM->read($length);
  192.     return (string)$data;
  193.   }
  194.   
  195.   /**
  196.    * Binary search for string
  197.    * 
  198.    * @access private
  199.    * @param string string
  200.    * @param int start (internally used in recursive function)
  201.    * @param int end (internally used in recursive function)
  202.    * @return int string number (offset in originals table)
  203.    */
  204.   function find_string($string, $start = -1, $end = -1) {
  205.     if (($start == -1) or ($end == -1)) {
  206.       // find_string is called with only one parameter, set start end end
  207.       $start = 0;
  208.       $end = $this->total;
  209.     }
  210.     if (abs($start - $end) <= 1) {
  211.       // We're done, now we either found the string, or it doesn't exist
  212.       $txt = $this->get_original_string($start);
  213.       if ($string == $txt)
  214.         return $start;
  215.       else
  216.         return -1;
  217.     } else if ($start > $end) {
  218.       // start > end -> turn around and start over
  219.       return $this->find_string($string, $end, $start);
  220.     } else {
  221.       // Divide table in two parts
  222.       $half = (int)(($start + $end) / 2);
  223.       $cmp = strcmp($string, $this->get_original_string($half));
  224.       if ($cmp == 0)
  225.         // string is exactly in the middle => return it
  226.         return $half;
  227.       else if ($cmp < 0)
  228.         // The string is in the upper half
  229.         return $this->find_string($string, $start, $half);
  230.       else
  231.         // The string is in the lower half
  232.         return $this->find_string($string, $half, $end);
  233.     }
  234.   }
  235.   
  236.   /**
  237.    * Translates a string
  238.    * 
  239.    * @access public
  240.    * @param string string to be translated
  241.    * @return string translated string (or original, if not found)
  242.    */
  243.   function translate($string) {
  244.     if ($this->short_circuit)
  245.       return $string;
  246.     $this->load_tables();     
  247.     
  248.     if ($this->enable_cache) {
  249.       // Caching enabled, get translated string from cache
  250.       if (array_key_exists($string, $this->cache_translations))
  251.         return $this->cache_translations[$string];
  252.       else
  253.         return $string;
  254.     } else {
  255.       // Caching not enabled, try to find string
  256.       $num = $this->find_string($string);
  257.       if ($num == -1)
  258.         return $string;
  259.       else
  260.         return $this->get_translation_string($num);
  261.     }
  262.   }
  263.  
  264.   /**
  265.    * Get possible plural forms from MO header
  266.    * 
  267.    * @access private
  268.    * @return string plural form header
  269.    */
  270.   function get_plural_forms() {
  271.     // lets assume message number 0 is header  
  272.     // this is true, right?
  273.     $this->load_tables();
  274.     
  275.     // cache header field for plural forms
  276.     if (! is_string($this->pluralheader)) {
  277.       if ($this->enable_cache) {
  278.         $header = $this->cache_translations[""];
  279.       } else {
  280.         $header = $this->get_translation_string(0);
  281.       }
  282.       if (eregi("plural-forms: (.*)\n", $header, $regs))
  283.         $expr = $regs[1];
  284.       else
  285.         $expr = "nplurals=2; plural=n == 1 ? 0 : 1;";
  286.       $this->pluralheader = $expr;
  287.     }
  288.     return $this->pluralheader;
  289.   }
  290.  
  291.   /**
  292.    * Detects which plural form to take
  293.    * 
  294.    * @access private
  295.    * @param n count
  296.    * @return int array index of the right plural form
  297.    */
  298.   function select_string($n) {
  299.     $string = $this->get_plural_forms();
  300.     $string = str_replace('nplurals',"\$total",$string);
  301.     $string = str_replace("n",$n,$string);
  302.     $string = str_replace('plural',"\$plural",$string);
  303.     
  304.     $total = 0;
  305.     $plural = 0;
  306.  
  307.     eval("$string");
  308.     if ($plural >= $total) $plural = 0;
  309.     return $plural;
  310.   }
  311.  
  312.   /**
  313.    * Plural version of gettext
  314.    * 
  315.    * @access public
  316.    * @param string single
  317.    * @param string plural
  318.    * @param string number
  319.    * @return translated plural form
  320.    */
  321.   function ngettext($single, $plural, $number) {
  322.     if ($this->short_circuit) {
  323.       if ($number != 1)
  324.         return $plural;
  325.       else
  326.         return $single;
  327.     }
  328.  
  329.     // find out the appropriate form
  330.     $select = $this->select_string($number); 
  331.     
  332.     // this should contains all strings separated by NULLs
  333.     $key = $single.chr(0).$plural;
  334.     
  335.     
  336.     if ($this->enable_cache) {
  337.       if (! array_key_exists($key, $this->cache_translations)) {
  338.         return ($number != 1) ? $plural : $single;
  339.       } else {
  340.         $result = $this->cache_translations[$key];
  341.         $list = explode(chr(0), $result);
  342.         return $list[$select];
  343.       }
  344.     } else {
  345.       $num = $this->find_string($key);
  346.       if ($num == -1) {
  347.         return ($number != 1) ? $plural : $single;
  348.       } else {
  349.         $result = $this->get_translation_string($num);
  350.         $list = explode(chr(0), $result);
  351.         return $list[$select];
  352.       }
  353.     }
  354.   }
  355.  
  356. }
  357.  
  358. ?>
  359.